package com.cl.pile.rule.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 基于fastjson的json工具
 *
 * @author chenyitao
 * @date 2020/12/7
 */
@Slf4j
public class JsonConvertUtil {

    /**
     * 是否允许数据覆盖
     */
    private boolean allowCoverage;

    private static final Pattern indexPattern = Pattern.compile("\\[(\\d*)]");

    public JsonConvertUtil() {
        this.allowCoverage = false;
    }

    public void setAllowCoverage(boolean allowCoverage) {
        this.allowCoverage = allowCoverage;
    }

    public static int getIndex(String fieldName) {
        Matcher matcher = indexPattern.matcher(fieldName);
        if (matcher.find() && StringUtils.isNotBlank(matcher.group(1))) {
            return Integer.parseInt(matcher.group(1));
        }
        return -1;
    }


    @AllArgsConstructor
    public static class PrintKeyParam {
        boolean objectSuffix;

        boolean arraySuffix;

        boolean arrayIndex;
    }

    public interface GetCallback {
        /**
         * @param depth
         * @param value
         * @param result
         * @return 返回true自动把值加到result/false手动增加
         */
        boolean valueMatch(int depth, Object value, List<Object> result);

        void noValueMatch(int depth, List<Object> result);
    }

    /**
     * 映射转换
     *
     * @param mappingMap 映射表
     * @param obj        需要转换的对象
     * @return
     */
    public JSONObject mappingToJson(Map<String, String> mappingMap, JSONObject obj) {
        JSONObject returnObj = new JSONObject();
        for (Map.Entry<String, String> entry : mappingMap.entrySet()) {
            String lKey = entry.getKey();
            String rKey = entry.getValue();
            List<Object> result = new ArrayList<>();
            getResultFromJson(obj, lKey, result);
            if (!result.isEmpty()) {
                setResultToJson(returnObj, rKey, result);
            }
        }
        return returnObj;
    }

    public void setResultToJson(JSONObject root, String fields, List<Object> result) {
        setResultToJson(root, fields.split("\\."), result, null, true);
    }

    public void setResultToJson(JSONObject root, String fields, Object result) {
        setResultToJson(root, fields.split("\\."), Lists.newArrayList(result), false, true);
    }

    public void setResultToJson(JSONObject root, String fields, List<Object> result, boolean mustList) {
        setResultToJson(root, fields.split("\\."), result, mustList, true);
    }

    /**
     * 为JSON赋值
     *
     * @param root      原始对象
     * @param fields    映射字段
     * @param result    赋值结果
     * @param mustList  强制数组
     * @param emptySkip 空数据不创建
     */
    public void setResultToJson(JSONObject root, String[] fields, List<Object> result,
                                Boolean mustList, Boolean emptySkip) {
        // 当数据为空时跳过结构创建与赋值
        if (emptySkip && result.isEmpty()) {
            return;
        }
        setJSONObject(root, fields, result, mustList, 0);
    }

    private void setJSONObject(JSONObject object, String[] fields, List<Object> result,
                               Boolean mustList, int depth) {
        String field = fields[depth];
        String cleanField = cleanKey(field);
        int targetIndex = getIndex(field);
        if (depth == fields.length - 1) {
            if (object.containsKey(cleanField) && !this.allowCoverage) {
                throw new RuntimeException("[" + String.join(".", fields) + "] 已存在数据 [" + object.get(cleanField) + "]");
            }
            if ((mustList != null && mustList) || result.size() > 1) {
                object.put(cleanField, result);
            } else if (result.size() == 1) {
                object.put(cleanField, result.get(0));
            }
            return;
        }

        if (field.endsWith("]")) {
            if (object.containsKey(cleanField)) {
                if (object.get(cleanField) instanceof JSONArray) {
                    while (object.getJSONArray(cleanField).size() - 1 < targetIndex) {
                        object.getJSONArray(cleanField).add(new JSONObject());
                    }
                    setJSONArray(object.getJSONArray(cleanField), fields, result, mustList, depth, targetIndex);
                } else {
                    throw new RuntimeException(field + "已存在数据但不是数组");
                }
            } else {
                if (targetIndex != 0) {
                    throw new RuntimeException(field + "数组不存在,自动创建进一步操作需要指定下标[0]");
                } else {
                    object.put(cleanField, new JSONArray());
                    object.getJSONArray(cleanField).add(new JSONObject());
                    setJSONArray(object.getJSONArray(cleanField), fields, result, mustList, depth, targetIndex);
                }
            }
        } else {
            if (!object.containsKey(cleanField)) {
                object.put(cleanField, new JSONObject());
            }
            setJSONObject(object.getJSONObject(cleanField), fields, result, mustList, depth + 1);
        }
    }

    private void setJSONArray(JSONArray array, String[] fields, List<Object> result,
                              Boolean mustList, int depth, int targetIndex) {
        if (depth == fields.length - 1) {
            if (!array.isEmpty() && !allowCoverage) {
                throw new RuntimeException("数组" + fields[depth] + "已存在数据无法覆盖");
            }
            array.clear();
            array.addAll(result);
            return;
        }
        int currentIndex = 0;
        for (Object o : array) {
            if (targetIndex == -1 || currentIndex == targetIndex) {
                setJSONObject((JSONObject) o, fields, result, mustList, depth + 1);
            }
            currentIndex++;
        }
    }


    public static void getResultFromJson(Object root, String fields, List<Object> result) {
        getResultFromJson(root, fields.split("\\."), result, 0, null);
        if (result.isEmpty()) {
            // 针对 a.b.c 字段深度查询未找到，切换为常规查询
            getResultFromJson(root, new String[]{fields}, result, 0, null);
        }
    }

    public void getResultFromJson(Object root, String fields, List<Object> result, GetCallback getCallback) {
        getResultFromJson(root, fields.split("\\."), result, 0, getCallback);
        if (result.isEmpty()) {
            // 针对 a.b.c 字段深度查询未找到，切换为常规查询
            getResultFromJson(root, new String[]{fields}, result, 0, getCallback);
        }
    }

    public void getResultFromJsonNoDepth(Object root, String fields, List<Object> result, GetCallback getCallback) {
        getResultFromJson(root, new String[]{fields}, result, 0, getCallback);
    }

    public void getResultFromJson(Object root, String[] fields, List<Object> result) {
        getResultFromJson(root, fields, result, 0, null);
    }

    /**
     * 从Json获取数据
     *
     * @param root        原始对象
     * @param fields      映射字段 **注意** 字段[数字]仅在完整设置下标情况下有效
     * @param result      结果
     * @param depth       起始深度
     * @param getCallback 取值回调
     */
    public static void getResultFromJson(Object root, String[] fields, List<Object> result, int depth, GetCallback getCallback) {
        if (root == null) {
            if (getCallback != null) {
                getCallback.noValueMatch(depth, result);
            }
            return;
        }
        if (depth == fields.length) {
            if (getCallback != null) {
                if (getCallback.valueMatch(depth, root, result)) {
                    result.add(root);
                }
            } else {
                result.add(root);
            }
            return;
        } else if (depth > fields.length) {
            if (getCallback != null) {
                getCallback.noValueMatch(depth, result);
            }
            return;
        }
        String currentDepth = fields[depth];
        String paramField = currentDepth.replaceAll("\\[\\d*]|\\{}", "");
        int targetIndex = getIndex(currentDepth);

        if (JSONArray.isValidArray(String.valueOf(root)) || root instanceof JSONArray) {
            for (Object o : ((JSONArray) root)) {
                Object child = ((JSONObject) o).get(paramField);
                invokeObject(child, fields, result, depth, getCallback);
            }
        } else if (JSONObject.isValid(String.valueOf(root)) || root instanceof JSONObject) {
            Object child = ((JSONObject) root).get(paramField);
            Object subChild = null;
            if (targetIndex >= 0 && child instanceof JSONArray) {
                int currentIndex = 0;
                for (Object o : ((JSONArray) child)) {
                    if (o == null) {
                        continue;
                    }
                    if (currentIndex == targetIndex) {
                        subChild = o;
                        break;
                    }
                    currentIndex++;
                }
            }
            if (subChild != null) {
                invokeObject(subChild, fields, result, depth, getCallback);
            } else {
                invokeObject(child, fields, result, depth, getCallback);
            }
        } else {
            log.error("无效类型:" + root.getClass().getName());
//            throw new RuntimeException("无效类型:" + root.getClass().getName());
        }
    }

    private static void invokeObject(Object child, String[] fields, List<Object> result, int depth, GetCallback getCallback) {
        if (child instanceof JSONObject) {
            getResultFromJson(child, fields, result, depth + 1, getCallback);
        } else {
            JSONObject jo = parseToJsonObj(String.valueOf(child));
            if (jo != null) {
                getResultFromJson(jo, fields, result, depth + 1, getCallback);
            } else {
                getResultFromJson(child, fields, result, depth + 1, getCallback);
            }
        }
    }

    private void deleteFromJson(Object root, Object parent, String[] fields, int depth) {
        if (depth == fields.length) {
            if (parent == null) {
                return;
            }
            boolean allowRemove = false;
            if (root == null || StringUtils.isBlank(root.toString())) {
                allowRemove = true;
            } else if (root instanceof JSONObject && ((JSONObject) root).isEmpty()) {
                allowRemove = true;
            } else if (root instanceof List) {
                allowRemove = ((List<?>) root)
                        .stream()
                        .filter(Objects::nonNull)
                        .noneMatch(s -> !(s instanceof JSONObject) || !((JSONObject) s).isEmpty());
            }
            if (allowRemove) {
                if (parent instanceof JSONArray) {
                    int targetIndex = getIndex(fields[depth - 2]);
                    ((JSONArray) parent).getJSONObject(targetIndex).remove(cleanKey(fields[depth - 1]));
                } else if (parent instanceof JSONObject) {
                    ((JSONObject) parent).remove(cleanKey(fields[depth - 1]));
                }
            }
            return;
        } else if (depth > fields.length) {
            return;
        }
        String currentDepth = fields[depth];
        String paramField = cleanKey(currentDepth);
        int targetIndex = getIndex(currentDepth);
        if (root instanceof JSONObject) {
            Object child = ((JSONObject) root).get(paramField);
            deleteFromJson(child, root, fields, depth + 1);
        } else if (root instanceof List) {
            int currentIndex = 0;
            for (Object o : ((List<?>) root)) {
                if (o == null) {
                    continue;
                }
                if (targetIndex == -1 || currentIndex == targetIndex) {
                    Object child = ((JSONObject) o).get(paramField);
                    deleteFromJson(child, root, fields, depth + 1);
                }
                currentIndex++;
            }
        } else {
//            throw new RuntimeException("无效类型:" + root.getClass().getName());
            log.error("无法操作的JSON节点");
        }
    }

    public void cleanJsonEmpty(JSONObject root) {
        PrintKeyParam pkp = new PrintKeyParam(true, true, true);
        Set<String> keys = printKey(root, pkp);
        Map<Integer, List<String[]>> fdMap = keys.stream().map(s -> s.split("\\.")).collect(
                Collectors.groupingBy(a -> a.length, Collectors.mapping(a -> a, Collectors.toList()))
        );
        int maxDepth = fdMap.keySet().stream().max(Integer::compareTo).get();
        while (maxDepth > 0) {
            if (fdMap.containsKey(maxDepth)) {
                List<String[]> filedList = fdMap.get(maxDepth);
                for (String[] fields : filedList) {
                    deleteFromJson(root, null, fields, 0);
                }
            }
            maxDepth--;
        }
    }

//    public static String getFieldValue(String field, Object dataObj) {
//        return getFieldValue(field, dataObj, ",");
//    }

    public static String getmFieldValue(String field,Object dataObj) {
        JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(dataObj));
        if (jsonObject.containsKey(field)) {
            return jsonObject.getString(field);
        } else if (jsonObject.containsKey(field.toLowerCase())){
            return jsonObject.getString(field.toLowerCase());
        } else if (jsonObject.containsKey(field.toUpperCase())){
            return jsonObject.getString(field.toUpperCase());
        } else {
            throw new RuntimeException("字段不存在:" + field + " keyset:" + jsonObject.keySet());
        }
    }
    public static String getFieldValue(String field,Object dataObj) {
        JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(dataObj));
        if (jsonObject.containsKey(field)) {
            return jsonObject.getString(field);
        } else if (jsonObject.containsKey(field.toLowerCase())){
            return jsonObject.getString(field.toLowerCase());
        } else if (jsonObject.containsKey(field.toUpperCase())){
            return jsonObject.getString(field.toUpperCase());
        } else {
            return null;
        }
    }

    /**
     * 直接获取字段值
     *
     * @param field
     * @param dataObj
     * @param delimiter
     * @return
     */
    public static String getFieldValue(String field, Object dataObj, String delimiter) {
        List<Object> result = new ArrayList<>();
        getResultFromJson(dataObj, field, result);
        return String.join(delimiter, result.stream().map(Object::toString).collect(Collectors.joining()));
    }

    /**
     * Map转Json
     * 对于字符串Json也进行转换
     *
     * @param dataMap
     * @return
     */
    public static JSONObject convertMapToJson(Map<String, Object> dataMap) {
        JSONObject root = new JSONObject();
        for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
            String eValue = String.valueOf(entry.getValue());
            if (eValue.startsWith("{")) {
                try {
                    JSONObject jsonObj = JSONObject.parseObject(eValue);
                    root.put(entry.getKey(), convertMapToJson(jsonObj.getInnerMap()));
                } catch (JSONException ignored) {
                }
            } else if (eValue.startsWith("[")) {
                try {
                    JSONArray jsonArr = JSONArray.parseArray(eValue);
                    JSONArray convertArr = new JSONArray();
                    for (Object o : jsonArr) {
                        String aValue = String.valueOf(o);
                        if (aValue.startsWith("{")) {
                            try {
                                JSONObject jsonObj = JSONObject.parseObject(eValue);
                                convertArr.add(convertMapToJson(jsonObj.getInnerMap()));
                            } catch (JSONException ignored) {
                                convertArr.add(o);
                            }
                        } else {
                            convertArr.add(o);
                        }
                    }
                    root.put(entry.getKey(), convertArr);
                } catch (JSONException ignored) {
                }
            } else {
                root.put(entry.getKey(), entry.getValue());
            }
        }
        return root;
    }

    //    ========分割========
    //    下面方法皆用于比对JSON

    private static JSONObject parseToJsonObj(String str) {
        try {
            return JSONObject.parseObject(str);
        } catch (JSONException e) {
            return null;
        }
    }

    public int compare(Set<String> endKeySet, JSONObject replyJson, JSONObject compareJson) {
        int source = 0;
        for (String endKey : endKeySet) {
            List<Object> distResult = new ArrayList<>();
            getResultFromJson(replyJson, endKey, distResult);
            List<Object> compareResult = new ArrayList<>();
            getResultFromJson(compareJson, endKey, compareResult);
            boolean isDistEmpty = isEmptyResult(distResult);
            boolean isCompareEmpty = isEmptyResult(compareResult);
            if (isDistEmpty && isCompareEmpty) {
//                System.out.println(endKey + " -> 空数据");
                source++;
            } else {
                for (Object o1 : distResult) {
                    for (Object o2 : compareResult) {
                        if (o1 instanceof JSONArray && o2 instanceof JSONArray) {
                            if (((JSONArray) o1).size() != ((JSONArray) o2).size()) {
                                continue;
                            }
                            int findCount = 0;
                            for (Object ino1 : ((JSONArray) o1)) {
                                if (findObjInList((JSONArray) o2, ino1)) {
                                    findCount++;
                                }
                            }
                            if (findCount == ((JSONArray) o1).size()) {
//                                System.out.println(endKey + " -> " + o1 + " = " + o2);
                                source++;
                            }
                        } else if (o1.equals(o2)) {
//                            System.out.println(endKey + " -> " + o1 + " = " + o2);
                            source++;
                        }
                    }
                }
            }
        }
        return source;
    }

    private <T extends List<?>> boolean findObjInList(T findList, Object findObj) {
        for (Object o : findList) {
            if (o == null && findObj == null) {
                return true;
            } else if (o == null || findObj == null) {
                return false;
            }
            if (o.equals(findObj)) {
                return true;
            }
        }
        return false;
    }

    private boolean isEmptyResult(List<Object> list) {
        if (list.isEmpty()) {
            return true;
        } else {
            int emptySize = 0;
            for (Object o : list) {
                if (o == null) {
                    emptySize++;
                    continue;
                }
                if ((o instanceof String) && o.toString().isEmpty()) {
                    emptySize++;
                }
            }
            return emptySize == list.size();
        }
    }

    private String cleanKey(String keys) {
        return keys.replaceAll("\\[\\d*]", "").replaceAll("\\{}", "");
    }

    public Set<String> extraEndValueKeys(JSONObject root, Set<String> keysSet) {
        Set<String> endKeySet = Sets.newHashSet();
        for (String keys : keysSet) {
            if (keys.endsWith("[]")) {
                List<Object> result = new ArrayList<>();
                getResultFromJson(root, cleanKey(keys), result);
                if (!result.isEmpty()) {
                    if (!(result.get(0) instanceof JSONObject)) {
                        endKeySet.add(cleanKey(keys));
                    }
                }
            } else if (!keys.endsWith("{}")) {
                endKeySet.add(cleanKey(keys));
            }
        }
        return endKeySet;
    }

    public <T extends JSON> Set<String> printKey(T obj) {
        PrintKeyParam pkp = new PrintKeyParam(true, true, false);
        return printKey(obj, pkp);
    }

    public <T extends JSON> Set<String> printKey(T obj, PrintKeyParam pkp) {
        Set<String> cSet = Sets.newHashSet();
        if (obj instanceof JSONObject) {
            printJsonObjectKey((JSONObject) obj, "", cSet, pkp);
        } else if (obj instanceof JSONArray) {
            printJsonArrayKey((JSONArray) obj, "", cSet, pkp);
        }
        return cSet;
    }

    public void printJsonObjectKey(JSONObject jo, String parent, Set<String> cSet, PrintKeyParam pkp) {
        for (String key : jo.keySet()) {
            String curKeys;
            if (parent.isEmpty()) {
                curKeys = key;
            } else {
                curKeys = parent + "." + key;
            }
            if (jo.get(key) instanceof JSONObject) {
                if (pkp.objectSuffix) {
                    curKeys += "{}";
                }
                printJsonObjectKey(jo.getJSONObject(key), curKeys, cSet, pkp);
                cSet.add(curKeys);
            } else if (jo.get(key) instanceof JSONArray || jo.get(key) instanceof List) {
                if (pkp.arraySuffix && !pkp.arrayIndex) {
                    curKeys += "[]";
                    cSet.add(curKeys);
                } else if (pkp.arrayIndex) {
                    cSet.add(curKeys + "[]");
                } else {
                    cSet.add(curKeys);
                }
                printJsonArrayKey(jo.getJSONArray(key), curKeys, cSet, pkp);
            } else {
                cSet.add(curKeys);
            }
//            System.out.println(curKeys);
        }
    }

    public void printJsonArrayKey(JSONArray ja, String parent, Set<String> cSet, PrintKeyParam pkp) {
        int currentIndex = 0;
        for (Object o : ja) {
            if (o instanceof JSONObject) {
                String tmpParent = parent;
                if (pkp.arrayIndex) {
                    tmpParent = parent.replaceAll("\\[]", "") + String.format("[%d]", currentIndex++);
                }
                printJsonObjectKey((JSONObject) o, tmpParent, cSet, pkp);
            }
        }
    }


}
